
<!-- 
	canvas_poster.vue
	用于生成固定模板海报
	zoucb 2023.03.20
	绘制步骤为线性依次绘制，不是同时绘制所有
 -->
<template>
	<view class="content">
		
		<view class="poster" style="width: 750rpx;height: 1256rpx;">
			<canvas canvas-id="canvas" id="canvas"></canvas>
		</view>
		
		<!-- #ifdef H5 -->
		<view class="poster_image" @tap="prevImg" id="poster_image">
			<block v-if="imgUrl">
				<block v-if="$isWeiXinBrower()">
					<image :src="imgUrl" mode="aspectFit">
					</image>
				</block>
				<block v-else>
					<canvas class="poster_image">
						<img :src="imgUrl" width="100%" height="100%" style="-webkit-touch-callout: default;"/>
					</canvas>
				</block>
			</block>
		</view>
		<!-- #endif -->
		
		<!-- #ifndef H5 -->
		<view class="poster_image" @tap="prevImg" >
			<image :src="imgUrl" width="100%" height="100%" style="-webkit-touch-callout: default;" id="poster_image"></image>
		</view>
		<!-- #endif -->
		
		<view class="uqrcode" width="width: 126px;height: 126px;">
			<canvas id="qrcode" canvas-id="qrcode"/>
		</view>
	</view>
</template>

<script>
	import uqrcode from './uqrcode.js'
	export default{
		props:{
			info:{
				type:Object,
				default:()=>{}
			}
		},
		data(){
			return{
				//canvas参数
				canvasInfo:{
					maxWidth:360,
					startX:27,
					startY:27,
					tWidth:414,
					tHeight:680,
				},
				
				//统计的计算高度，数组
				calHeight:[],
				
				imgUrl:'',
				
				
				qrCode:''
				
				
			}
		},
		
		watch:{
			info:{
				handler(nv,ov){
					this.posterInfo = nv
				},
				deep:true
			}
		},
		
		mounted(){
			this.posterInfo = this.info
			if(this.posterInfo.posterCache){
				this.imgUrl = this.posterInfo.posterCache
				return
			}
			
			this.iniCanvas()
		},
		
		methods:{
			iniCanvas(){
				this.ctx = uni.createCanvasContext('canvas',this)
				this.drawBackground()
			},
			
			drawBackground(){
				this.ctx.setFillStyle('#ffffff');
				this.ctx.fillRect(0, 0, this.canvasInfo.tWidth, this.canvasInfo.tHeight);
				this.ctx.save()
				this.drawText_name()
			},
			
			setFontSize(size){
				this.ctx.font = `400 ${size}px PingFangSC-Regular, "Microsoft YaHei", Helvetica, "Droid Sans", Arial, sans-serif`
			},
			
			/**
			 * 用于绘制文字
			 * @param 
			 * 	   text: 文本
			 * 	   size: 字号大小
			 * 	   maxWidth: 文本的最大长度
			 *     x、y: 文本所在位置
			 *     isWrap: 是否折行,最大行数2
			 *     noCal: 绘制该文本时，不去统计该文本所在位置的纵轴Y值 
			**/
			drawText(text,size,maxWidth,x,y,isWrap,noCal){
				
				const find_wrap_index = (_text)=>{
					//该文本如果长度大于最大宽度(maxWidth有值)的话，则找出超过最大宽度时的那个单字
					let index = _text.split('').findIndex((val,idx)=>{
						let width = this.ctx.measureText(_text.substring(0,idx+1)).width
						return width>=maxWidth
					})
					return index
				}
				
				
				if(maxWidth){
					//如果有传入的文本最大宽度，则需要考虑文本是折行还是省略
					const index1 = find_wrap_index(text)
					if(index1>0){
						let single = text.substring(0,index1)
						if(isWrap){
							this.ctx.fillText(single, x, y)
							!noCal&&(this.calHeight.push(y+size))
						
							let left = text.substring(index1)
							const index2 = find_wrap_index(left)
							if(index2>0){
								left = left.substring(0,index2-1) + '...'
							}
							this.ctx.fillText(left, x, y+size+6)
							!noCal&&(this.calHeight.push(y+2*size+6))
						}else{
							let final_single = single.substring(0,single.length-1) + '...'
							this.ctx.fillText(final_single, x, y)
							!noCal&&(this.calHeight.push(y+size))
						}
					}else{
						this.ctx.fillText(text, x, y)
						!noCal&&(this.calHeight.push(y+size))
					}
				}else{
					//否则直接绘制文字
					this.ctx.fillText(text, x, y)
					!noCal&&(this.calHeight.push(y+size))
				}
				
			},
			
			/**
			 * 用于绘制主图，以完整的图缩放展示
			 * @param 
			 *     x、y: 主图所在位置
			 * 	   image:通过 image
			**/
			drawMainImage(image,x,y,width,height){
				const {maxWidth,startX} = this.canvasInfo
				const rect = maxWidth
				let ratio = width/height
				let drawParam = {}
				
				if(ratio>1){
					//比例大于1，则满宽，高度按比例缩放
					drawParam.width = maxWidth
					drawParam.height = maxWidth / ratio
					
					//纵轴y值计算使其居中
					drawParam.x = x
					drawParam.y = y + rect/2 - drawParam.height/2
				}else{
					//比例大于1，则满高，宽度按比例缩放
					drawParam.width = maxWidth * ratio
					drawParam.height = maxWidth
					
					//横轴x值计算使其居中
					drawParam.x = x + rect/2 - drawParam.width/2
					drawParam.y = y
				}
				this.calHeight.push(y+maxWidth)
				this.ctx.drawImage(image,drawParam.x, drawParam.y, drawParam.width, drawParam.height)
			},
			
			getUid(prefix) {
			    prefix = prefix || '';
			
			    return (
			        prefix +
			        'xxyxxyxx'.replace(/[xy]/g, c => {
			            let r = (Math.random() * 16) | 0;
			            let v = c === 'x' ? r : (r & 0x3) | 0x8;
			            return v.toString(16);
			        })
			    );
			},
			
			
			getBase64Image(base64){
				let uid = Math.random()
				let filename = `${wx.env.USER_DATA_PATH}/${this.getUid()}.png`;
				
				let fileManager = wx.getFileSystemManager();
				
				return new Promise((resolve,reject)=>{
					fileManager.writeFile({
					    filePath: filename,
					    data: base64.replace(/data:image\/(.*);base64,/, ''),
					    encoding: 'base64',
					    success (res) {
							resolve(filename)
					    },
					    fail (res) {
					        reject(res);
					    },
					})
				})
			},
			
			
			/**
			 * 每次绘制时，将绘制的 纵轴y值加上字体或者图片的高度 push进calHeight
			 * 而 calHeightPos 将返回最大值，用于下个绘制时的纵轴Y值计算，
			 * @param 
			 *     pos: 如果有该值则 已这个值的前的数值为区间得出最大值
			 * 			用于 绘制和上一个绘制目标位置平行的 目标
			**/
			calHeightPos(pos){
				if(pos){	 
					return Math.max.apply({},this.calHeight.slice(0,pos))
				}else{
					return Math.max.apply({},this.calHeight)
				}
			},
			
			//绘制商品名
			drawText_name(){
				let {name} = this.posterInfo
				const {maxWidth,startX} = this.canvasInfo
				this.ctx.setTextBaseline('top')
				this.setFontSize(23)
				this.ctx.setFillStyle('#333333');
				this.drawText(name,23,maxWidth,startX,27,true)
				this.ctx.save()
				this.drawText_price()
			},
			
			//绘制价格
			drawText_price(){
				let {price,marketPrice} = this.posterInfo
				this.setFontSize(23)
				this.ctx.setFillStyle(this.diyStyle_var['--color_price']);
				this.drawText(price,23,null,27,this.calHeightPos()+23,true)
				this.ctx.save()
				if(marketPrice){
					this.drawText_marketPrice()
				}else{
					this.drawImage_main()
				}
			},
			
			//绘制市场价或者其他划线价格
			drawText_marketPrice(){
				const {price,marketPrice} = this.posterInfo
				const {startX} = this.canvasInfo
				const {calHeight,calHeightPos} = this
				this.setFontSize(17)
				this.ctx.setFillStyle('rgb(148, 148, 148)');
				
				let position_x = this.ctx.measureText(price).width + 50 + startX
				let position_y = calHeightPos(calHeight.length-1) + 26
				this.drawText(marketPrice,17,null,position_x,position_y,true,true)
				let textWidth = this.ctx.measureText(marketPrice).width
				
				this.ctx.beginPath()
				// #ifdef MP-WEIXIN
				this.ctx.rect(position_x, position_y+(20/2), textWidth, 1);
				// #endif
				// #ifdef APP-PLUS
				this.ctx.rect(position_x, position_y+(23/2), textWidth, 1);
				// #endif
				// #ifdef H5
				this.ctx.rect(position_x, position_y+(23/2/2), textWidth, 1);
				// #endif
				this.ctx.fill()
				this.ctx.save()
				this.drawImage_main()
			},
			
			// 绘制商品主图
			drawImage_main(){
				const {startX,maxWidth} = this.canvasInfo
				const {image} = this.posterInfo
				const {calHeight,calHeightPos} = this
				this.ctx.rect(startX, calHeightPos()+20, maxWidth, maxWidth)
				this.ctx.clip()
				
				this.loadMainImage().then(res=>{
					// #ifdef MP-WEIXIN
					this.drawMainImage(res.path,startX,calHeightPos()+20,res.width,res.height)
					// #endif
					// #ifdef H5
					this.drawMainImage(image,startX,calHeightPos()+20,res.width,res.height)
					// #endif
					// #ifdef APP-PLUS
					const obj = {...res[0],...res[1]}
					this.drawMainImage(obj.downUrl,startX,calHeightPos()+20,obj.width,obj.height)
					// #endif
					this.ctx.restore()
					this.ctx.save()
					this.drawImage_code()
				})
				
				
				
			},
			
			//绘制二维码或者太阳码
			drawImage_code(){
				const {qrcode,brief} = this.posterInfo
				const {startX} = this.canvasInfo
				const {calHeightPos} = this
				// #ifdef MP-WEIXIN
				if(!qrcode){
					this.$api.msg('请等待资源加载完成后重试')
					return
				}
				
				this.getBase64Image(qrcode).then((res)=>{
					this.ctx.drawImage(res,startX, calHeightPos() + 30, 126, 126)
					this.ctx.restore()
					this.ctx.save()
					if(brief){
						this.drawText_brief()
					}else{
						this.drawText_tip(false)
					}
				})
				
				
				// #endif
				
				// #ifndef MP-WEIXIN
				this.makeQrCode().then(res=>{
					const qrCode = res.tempFilePath
					this.qrCode = res.tempFilePath
					this.ctx.drawImage(qrCode,startX, calHeightPos() + 30, 126, 126)
					this.ctx.restore()
					this.ctx.save()
					if(brief){
						this.drawText_brief()
					}else{
						this.drawText_tip(false)
					}
				})
				// #endif
				
				
				
				
			},
			//绘制商品描述，没有则跳过
			drawText_brief(){
				const {brief} = this.posterInfo
				const {calHeightPos} = this
				const {maxWidth,startX} = this.canvasInfo
				this.setFontSize(22)
				this.ctx.setFillStyle('#333333');
				let position_x = startX + 126 + 30
				let position_y = calHeightPos() + 60
				let textWidth = maxWidth - (126 + 30)
				this.drawText(brief,22,textWidth,position_x,position_y,false)
				this.ctx.save()
				this.drawText_tip(true)
			},
			
			//绘制提示，isBrief则判断有无商品描述时，绘制提示的纵轴Y值
			drawText_tip(isBrief){
				const {calHeightPos} = this
				const {maxWidth,startX} = this.canvasInfo
				this.setFontSize(14)
				const text_tip = '长按/扫描二维码了解商品详情'
				this.ctx.setFillStyle('rgb(153, 153, 153)')
				let position_x = startX + 126 + 30
				let position_y = calHeightPos() + (isBrief?30:85)
				this.drawText(text_tip,14,null,position_x,position_y,false)
				this.ctx.save()
				this.ctx.draw()
				//完成后生成图片
				this.saveCanvasToImage()
			},
			
			
			//获取商品主图的信息，包括宽高
			loadMainImage(){
				const {image} = this.posterInfo
				const systemInfo = uni.getSystemInfoSync()
				const p1 = new Promise((resolve,reject)=>{
					let _this = this
					uni.getImageInfo({
						src:image,
						success(res){
							resolve({
								url:image,
								width:res.width,
								height:res.height,
								path:res.path
							})
						},
						fail(err){
							throw new Error('获取商品主图错误')
						}
					})
				})
				
				// #ifdef APP-PLUS
				const p2 = new Promise((resolve,reject)=>{
					uni.downloadFile({
						url:image,
						success(res) {
							resolve({
								downUrl:res.tempFilePath
							})
						},
						fail() {
							throw new Error('获取商品主图错误')
						}
					})
				})
				
				
				return Promise.all([p1,p2])
				// #endif
				
				// #ifndef APP-PLUS
				return p1
				// #endif
				
			},
			
			//生成图片
			saveCanvasToImage () {
			    let self = this;       
				const {tWidth,tHeight} = this.canvasInfo
				const device = uni.getSystemInfoSync && wx.getSystemInfoSync() || {};
				const delay = device.system.indexOf('iOS') === -1 ? 300 : 100
			    // 延时保存有两个原因，一个是等待绘制delay的元素，另一个是安卓上样式会错乱
			    setTimeout(() => {			
			        let obj = {
			            x: 0,
			            y: 0,
			            width: tWidth,
			            height: tHeight,
			            canvasId: 'canvas',
			            success: function (res) {
			                self.imgUrl = res.tempFilePath;
							self.$emit('cachePoster',self.imgUrl)
							uni.hideLoading()
			            },
			            fail: function (res) {
			                console.log(res,'error')
						}
			        }
			        uni.canvasToTempFilePath(obj, this);
			    }, delay);
			},
			
			//生成二维码
			makeQrCode(options) {
				const {url} = this.posterInfo
				return uqrcode.make({
					canvasId: 'qrcode',
					size: 126,
					margin: 10,
					text: url
				}, this)
			},
			
			prevImg() {
				uni.previewImage({
					urls: [this.imgUrl]
				})
			},
			
			//保存图片--小程序 app
			savePoster() {
				const that = this
				if(!this.imgUrl){
					return
				}
				
				uni.saveImageToPhotosAlbum({
					filePath: this.imgUrl,
					success: function() {
						wx.showToast({
							title: '保存成功',
							icon: 'none',
							duration: 1500
						});
					},
					fail(err) {
						if (err.errMsg === "saveImageToPhotosAlbum:fail:auth denied" || err.errMsg ===
							"saveImageToPhotosAlbum:fail auth deny" || err.errMsg ===
							"saveImageToPhotosAlbum:fail authorize no response") {
							uni.showModal({
								title: '提示',
								content: '需要您授权保存相册',
								showCancel: false,
								success: modalSuccess => {
									uni.openSetting({
										success(settingdata) {
											if (settingdata.authSetting[
													'scope.writePhotosAlbum']) {
												uni.saveImageToPhotosAlbum({
													filePath: that.data.imgUrl,
													success: function() {
														uni.showToast({
															title: '保存成功',
															icon: 'success',
															duration: 2000
														})
													},
												})
											} else {
												wx.showToast({
													title: '授权失败，请稍后重新获取',
													icon: 'none',
													duration: 1500
												});
											}
										}
									})
								}
							})
						}
					}
				})
			},
		}
	}
</script>

<style lang="scss">
	.content{
	}
	.poster{
		
		position: absolute;
		left: 10000px;
		visibility: hidden;
		height: 0;
		overflow: hidden;
		
		canvas{
			width: 414px;
			height: 680px;
		}
	}
	
	
	.poster_image{
		canvas{
			width: 500rpx;
			height: 800rpx;
		}
		
		image,img{
			width: 500rpx;
			height: 800rpx;
		}
	}
	
	.uqrcode{
		position: absolute;
		left: 10000px;
		visibility: hidden;
		height: 0;
		overflow: hidden;
		
		#qrcode{
			width: 126px;
			height: 126px;
		}
	}
</style>